# -*- encoding: utf8 -*-
"""

.. module:: Parse
    :synopsis: Parsing utilities

.. moduleauthor:: Ruymán Reyes Castro <rreyes@ull.es>
.. moduleauthor:: Juan José Fumero Alfonso <jjfumero@gmail.com>

"""

from yacf.Frontend.Common.Prepro import Prepro,PreproError
import subprocess
from cStringIO import StringIO
import config
import re
import os

# Default include path
INCLUDEPATH = ['.', config.INCLUDE_DIR]


class C99Prepro(Prepro):
	
    includes = {}
    
    @staticmethod
    def removeComments(string):
        def replacer(match):
            s = match.group(0)
            if s.startswith('/'):
                return ""
            else:
                return s
        pattern = re.compile(
            r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
            re.DOTALL | re.MULTILINE
        )
        return re.sub(pattern, replacer, string)
    
    @staticmethod
    def remove_includes(source_code):
        """ Remove system includes from C sources
            Current removed includes match the following regexp::
            
                        #include <[A-Za-Z0-9.]+>
        """      
        identifier = "[A-Za-z0-9.-_]+"
        include_sys = '#include <' + identifier + '>'
        include_re = re.compile(include_sys)
        no_include = []
        for line in source_code.split('\n'):
            if not include_re.match(line):
                no_include += [line]
        return "\n".join(no_include)


#    @staticmethod
#    def comment_includes(source_code):
#        """ Remove system includes from C sources
#            Current removed includes match the following regexp::
#            
#                        #include <[A-Za-Z0-9.]+>
#        """      
#        identifier = "[A-Za-z0-9.-_]+"
#        include_sys = '#include <' + identifier + '>'
#        include_re = re.compile(include_sys)
#        commented_include = []
#        for line in source_code.split('\n'):
#            if not include_re.match(line):
#                commented_include += [line]
#            else:
#                commented_include += ["/* ##### YACF " + line + " YACF ##### */", ]
#        return "\n".join(commented_include)


    @staticmethod
    def comment_includes(source_code):
        """ Remove system includes from C sources
                        #include <[A-Za-Z0-9.]+>
        """      
        yacfA = '/* ##### YACF '
        yacfB = ' YACF ##### */'
        def yacfComment(matchobj):
            return yacfA + '#include <'+ matchobj.group('name') + '.h>' + yacfB
        source_yacf_comment = re.sub("#include <(?P<name>[A-Za-z0-9-_]+(/[A-Za-z0-9-_]+)*).h>", yacfComment , source_code)

        return source_yacf_comment


    @staticmethod
    def search(filename):
        current_path = os.getcwd()
        dirsl = len(INCLUDEPATH)
        #~ print "Looking for " + str(filename) + " in " + str(INCLUDEPATH )
        for i in INCLUDEPATH:
            try:
                #~ print " Trying to enter " + str(i) 
                os.chdir(i)
                try:
                    #~ print " Trying to open " + str(filename) + " in " + str(i)
                    f = open(filename)
                    os.chdir(current_path)
                    #~ print " Success trying to open " + str(filename) + " in " + str(i)
                    return f
                except IOError:
                    #~ print " IO Eror with " + str(filename) + " in " + str(current_path)
                    pass
            except OSError:
                print 'Directory ' + i + ' does not exist'
        #~ print " Not found " + str(filename) + " in " + str(INCLUDEPATH)
        os.chdir(current_path)
        return None


    @staticmethod
    def comment_includes_recursive(source_code, source_list=[]):
        l = source_list
        yacfA = '/* ##### YACF '
        yacfB = ' YACF ##### */'
        def yacfComment(matchobj):
            return yacfA + '#include <'+ matchobj.group('name') + '.h>' + yacfB
        source_code = re.sub("#include([ \t]*)<(?P<name>[A-Za-z0-9-_]+(/[A-Za-z0-9-_]+)*).h>", yacfComment , source_code)
 
        l.append(source_code)
        source_list = l

        def localIncludes(m):
            include = m.group('name')
            f = C99Prepro.search(include)
            if f != None:
                sc = f.read()
                source_list = l
                (sc, l2) = C99Prepro.comment_includes_recursive(sc, source_list)
                return yacfA + '#include "' + include + '".h' + yacfB + '\n' + sc
            else:
                print 'Error: file ' + include +' not found.'
  
        source_yacf_comment = re.sub('#include([ \t]*)"(?P<name>[A-Za-z0-9-_]+(/[A-Za-z0-9-_]+)*.h)"', localIncludes, source_code)
        return (source_yacf_comment, l)


    @staticmethod
    def uncomment_includes(source_code):
        """ Remove system includes from C sources
            Current removed includes match the following regexp::
            
                        #include <[A-Za-Z0-9.]+>
        """      
        yacfA = '/\* ##### YACF '
        yacfB = ' YACF ##### \*/'
        def name(matchobj):
            return '#include <'+ matchobj.group('name') + '.h>'
        source_yacf_uncomment = re.sub(yacfA + "#include <(?P<name>[A-Za-z0-9-_]+(/[A-Za-z0-9-_]+)*).h>" + yacfB, name , source_code)

        return source_yacf_uncomment

    @staticmethod
    def lineLastInclude(source_code):
        s = source_code.split('\n')
        last = 0;
        include=''
        for l in range(0,len(s)):
            m = re.match(r'[ \t]*#include[ \t]*[<"](?P<name>[A-Za-z0-9-_]+(/[A-Za-z0-9-_]+)*).h[>"]', s[l])
            if (m != None):
                include = m.group('name')+'.h'
                last = l
        return last+1, include

    @staticmethod
    def resolve(source_code):
        noIncludes = True
        listLocalNew = []
        (linelast, inc) = C99Prepro.lineLastInclude(source_code)
        if (inc != ''):
            noIncludes = False
 
        def globalInclude(matchobj):
            includeName = matchobj.group('name')+'.h'
            C99Prepro.includes['<'+includeName+'>'] = ' ' 
            return '#include <'+ matchobj.group('name') + '.h>'
    
        def localIncludes(m):
            include = m.group('name')
            listLocalNew.append('"'+include+'"')
            f = C99Prepro.search(include)
            if f != None:
                C99Prepro.includes['"'+include+'"'] = ''
                return '#include "' + include  + '\n'
            else:
                print 'Error: file ' + include +' not found.'
        
        if (noIncludes == False):
            source_code = re.sub("#include([ \t]*)<(?P<name>[A-Za-z0-9-_]+(/[A-Za-z0-9-_]+)*).h>", globalInclude, source_code)
            source_yacf = re.sub('#include([ \t]*)"(?P<name>[A-Za-z0-9-_]+(/[A-Za-z0-9-_]+)*.h)"', localIncludes, source_code)
       
        # Procesar los includes
        for i in C99Prepro.includes:
            preprocess = config.PREPROCESSOR + ' -I' + config.INCLUDE_DIR + " - "
            p = subprocess.Popen(preprocess, shell=True, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True)
            source_in = "#include " + i 
            C99Prepro.includes[i] = p.communicate(source_in)[0]
            # print C99Prepro.includes[i]
    
        #Resolver el codigo general en caso de que haya MACROS
        preprocess = config.PREPROCESSOR + ' -I' + config.INCLUDE_DIR + " - "
        #~ print preprocess
        p = subprocess.Popen(preprocess, shell=True, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True)
        source_aux = p.communicate(source_code)[0]
        #~ print source_aux
        if (noIncludes == False):
            cadena_buscar = '# ' + str(linelast+1)  + ' "<stdin>"'
            ind = source_aux.rindex(cadena_buscar)
            ind2 = ind +1
            salir = False
            while not salir:
                if (source_aux[ind2] == '\n'):
                    salir = True
                ind2 += 1
            source_yacf = source_aux[ind2:]
        else:
            source_yacf = source_aux       
     
        # If INSERT_LOCAL_INCLUDES in config_local is True, the source code
        # in the local include resolved is inserted into general source code
        # to parser.
        if (config.INSERT_LOCAL_INCLUDES == True):
            for i in listLocalNew:
                if i in C99Prepro.includes:
                    source_yacf = C99Prepro.includes[i] + '\n' + source_yacf 
                    
        if config.SHOW_FILE_AFTER_PREPRO:
            print source_yacf
        return (source_yacf, C99Prepro.includes)



    @staticmethod
    def preprocess(code_source):
        """Apply preprocessor
            :param code_source: string with the source code
            .. warning::
                This function requires *cpp* to be in PATH.
        """
        # prepro = "cpp -w -ansi -pedantic -CC -U __USE_GNU  -DLLC_TRANSLATION -P -I"
        import os
        arch = os.uname()[-1]
        prepro = "gcc -std=c99 -E -trigraphs  -pedantic  -w -ansi -CC -xc - -D " + str(arch) + " -DLLC_TRANSLATION -P"
        # prepro = "gcc  -E -trigraphs  -pedantic  -w -ansi -CC -xc - -D __x86_64__ -DLLC_TRANSLATION -P -I"
        # prepro = "gcc -E -trigraphs  -CC  -xc - -D _x86_64_ -DLLC_TRANSLATION -P"
        # default_include_file = config.WORKDIR +  'Frontend/include/llcomp_translation.h'
        # Prepare preprocessor pipe
        prepro_line = prepro +  str(" ".join([" -I " + str(elem) for elem in INCLUDEPATH])) + " -I " + config.INCLUDE_DIR
        if config.COMMENT_INCLUDES and not config.GNU_SUPPORT:
            prepro_line +=  " -I " + config.FAKE_LIBC 
#        print " Prepro line : " + str(prepro_line)
# + ' -include ' + default_include_file
        p = subprocess.Popen(prepro_line, shell=True, bufsize=1, 
            stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True)
        # Comment system includes to avoid removal by preprocessor
        if config.COMMENT_INCLUDES:
            (code_source, l_includes) = C99Prepro.comment_includes_recursive(code_source, [])
            code_source = C99Prepro.comment_includes(code_source)
        # Send / Retrieve string to the preprocessor
        stripped_source = p.communicate(code_source)[0]
        if p.returncode > 0:
            print "Preprocessor Error "
            raise PreproError

        if config.COMMENT_INCLUDES:
            stripped_source = C99Prepro.uncomment_includes(stripped_source)

        if config.SHOW_FILE_AFTER_PREPRO:
            print stripped_source
        return stripped_source


    @staticmethod
    def preprocess2(code_source):
        (stripped_source, rincludes) = C99Prepro.resolve(code_source)
        return stripped_source


    @staticmethod
    def writeIncludes(code_source):
        strincludes = ''
        for i in C99Prepro.includes:
            strincludes += '#include ' + i + '\n'

        code_source = strincludes + code_source
        return code_source
      
 
